package com.toplabs.unashu.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.toplabs.unashu.common.Constants;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;

import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author toplabs
 * @since 2022/06/15
 * <p>This java file was created by toplabs in 2022/06/15.
 * The following is the description information about this file:</p>
 * <p>description: custom tool class</p>
 */
@Slf4j
@SuppressWarnings("unused")
public class TopLabsUtils {

    private static final int STANDARD_FILE_SIZE = 1024;

    private TopLabsUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * Ignore the results
     *
     * @param result result
     */
    public static void ignore(boolean result) {
        log.debug("result ignore: " + result);
    }

    /**
     * Determine whether the system is successfully installed.
     *
     * @return result
     */
    public static boolean isInstalled() {
        String path = getFilePath(Constants.STRING_EMPTY, true) + File.separator + Constants.MARK_FILE;
        File file = new File(path);
        return file.exists();
    }

    public static void marked() throws IOException {
        String markFile = getFilePath(Constants.STRING_EMPTY, true) + Constants.SEPARATOR + Constants.MARK_FILE;
        File file = new File(markFile);
        if (!file.exists() && !file.createNewFile()) {
            throw new IOException("create new file error");
        }
    }

    /**
     * Load the properties file under the classpath according to the file name.
     *
     * @param fileName filename
     * @return properties file
     */
    public static Properties loadPropertyFile(String fileName) {
        String path = getFilePath(fileName, true);
        Properties prop = new Properties();
        try (InputStream input = Files.newInputStream(Paths.get(path))) {
            prop.load(input);
            return prop;
        } catch (IOException e) {
            log.error("Failed to load Property file: ", e);
            return new Properties();
        }
    }

    /**
     * Set properties file value according to key.
     *
     * @param filename file name
     * @param kv       key-value collection
     * @return true/false
     */
    public static boolean setPropertyFile(String filename, Map<String, String> kv) {
        String filePath = getFilePath(filename, true);
        if (!CollUtil.isEmpty(kv)) {
            try (InputStream input = new BufferedInputStream(Files.newInputStream(Paths.get(filePath))); FileOutputStream output = new FileOutputStream(filePath)) {
                Properties prop = new Properties();
                prop.load(input);
                Set<String> keys = kv.keySet();
                keys.forEach(key -> prop.put(key, kv.get(key)));
                prop.store(output, "update properties");
                output.flush();
                return new File(filePath).setReadOnly();
            } catch (IOException e) {
                log.error("Property file write failed: ", e);
            }
        }
        return false;
    }


    public static String md5(String input) {
        return encrypt(input);
    }

    public static String md5(String input, String salt) {
        return encrypt(encrypt(input) + encrypt(salt));
    }

    /**
     * Convert date to cron expression
     *
     * @param date date
     * @return cron expression
     */
    public static String toCronExp(Date date) {
        String defaultExp = "0/1 * * * * ? *";
        if (Objects.nonNull(date)) {
            defaultExp = Constants.CRON_DATE_FORMAT.get().format(date);
        }
        return defaultExp;
    }

    /**
     * Convert Cron expression to Date
     *
     * @param expression cron expression
     * @return Date
     */
    public static Date cronExpToDate(String expression) {
        try {
            SimpleDateFormat cronFormat = new SimpleDateFormat("ss mm HH dd MM yyyy");
            SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
            return cronFormat.parse(expression.replace("?", yearFormat.format(DateUtil.date())));
        } catch (ParseException e) {
            return DateUtil.date();
        }
    }

    /**
     * get file size
     *
     * @param size size
     * @return {@link String}
     */
    public static String fileSize(long size) {
        if (size < STANDARD_FILE_SIZE) {
            return size + "Bytes";
        }
        if (size < STANDARD_FILE_SIZE * STANDARD_FILE_SIZE) {
            return (int) size / STANDARD_FILE_SIZE + "KB";
        }
        if (size < STANDARD_FILE_SIZE * STANDARD_FILE_SIZE * STANDARD_FILE_SIZE) {
            return (int) size / (STANDARD_FILE_SIZE * STANDARD_FILE_SIZE) + "MB";
        }
        return size / (STANDARD_FILE_SIZE * STANDARD_FILE_SIZE * STANDARD_FILE_SIZE) + "TB";
    }

    /**
     * a random string of length len
     *
     * @param len len
     * @return {@link String}
     */
    public static String random(int len) {
        if (len <= 0) {
            len = 8;
        }
        try {
            StringBuilder val = new StringBuilder();
            Random random = SecureRandom.getInstanceStrong();
            for (int i = 0; i < len; i++) {
                String temp = random.nextInt(2) % 2 == 0 ? "number" : "char";
                if ("char".equalsIgnoreCase(temp)) {
                    int nextInt = random.nextInt(2) % 2 == 0 ? 65 : 97;
                    val.append((char) (nextInt + random.nextInt(26)));
                } else {
                    val.append(random.nextInt(10));
                }
            }
            return val.toString().toLowerCase();
        } catch (NoSuchAlgorithmException e) {
            log.error("failed to create random object: ", e);
            return Constants.STRING_EMPTY;
        }
    }

    /**
     * Obtain the file path under the classpath according to the file name.
     *
     * @param name file name
     * @return file path
     */
    public static String getFilePath(String name) {
        return getFilePath(name, false);
    }

    /**
     * Obtain the file path under the classpath according to the file name.
     *
     * @param name     file name
     * @param isCreate file is created
     * @return file path
     */
    public static String getFilePath(String name, boolean isCreate) {
        URL url = TopLabsUtils.class.getClassLoader().getResource("");
        String path = "";
        if (url != null) {
            String classpath = new File(url.getPath()).getPath();
            path = classpath + File.separator + name;
            // does not exist or not
            if (isCreate) {
                createFile(path);
            }
        }
        return path;
    }

    /**
     * create a file
     *
     * @param path path
     */
    public static void createFile(String path) {
        File file = new File(path);
        // file does not exist
        if (!file.exists()) {
            // if the file ends with a delimiter, it is a folder
            if (path.endsWith(File.separator)) {
                // new folder
                ignore(file.mkdirs());
            } else {
                // is a file, to determine whether the folder where the file is located exists
                if (!file.getParentFile().exists()) {
                    // create if it doesn't exist
                    ignore(file.getParentFile().mkdirs());
                }
                try {
                    // create a new file
                    ignore(file.createNewFile());
                } catch (IOException e) {
                    log.error("path: {}, new file IOException: {}", path, e);
                }
            }
        }
    }

    /**
     * determine whether the jar file is started
     *
     * @param clazz clazz
     * @return boolean
     */
    public static <T> boolean isStartupFromJar(Class<T> clazz) {
        URL url = clazz.getResource(Constants.STRING_EMPTY);
        if (url == null) {
            return false;
        }
        String protocol = url.getProtocol();
        return Objects.equals(protocol, "jar");
    }

    /**
     * encrypt
     *
     * @param input input
     * @return {@link String}
     */
    private static String encrypt(String input) {
        byte[] code;
        try {
            code = MessageDigest.getInstance(Constants.ALGORITHM_NAME).digest(input.getBytes());
        } catch (NoSuchAlgorithmException e) {
            code = input.getBytes();
        }
        return byteToHexString(code);
    }

    /**
     * get text data
     *
     * @param content content
     * @return int
     */
    public static int getWords(String content) {
        int count;
        String cnWords = content.replaceAll("[^(\\u4e00-\\u9fa5，。《》？；’‘：“”【】、）（…￥！·)]", "");
        int cnWordsCount = cnWords.length();
        String nonCnWords = content.replaceAll("[^a-zA-Z0-9`\\-=';.,/~!@#$%^&*()_+|}{\":><?\\[\\]]", " ");
        int nonCnWordsCount = 0;
        String[] temp = nonCnWords.split(" ");
        for (String ch : temp) {
            if (ch.trim().length() != 0) {
                nonCnWordsCount++;
            }
        }
        count = cnWordsCount + nonCnWordsCount;
        return count;
    }

    /**
     * 将字节数组转化为十六进制字符串
     *
     * @param digest 待转化数组
     * @return {@link String}
     */
    public static String byteToHexString(byte[] digest) {
        StringBuilder buf = new StringBuilder();
        for (byte b : digest) {
            String tempStr = Integer.toHexString(b & 0xff);
            if (tempStr.length() < 2) {
                buf.append("0");
            }
            buf.append(tempStr);
        }
        return buf.toString();
    }

    /**
     * 首字母转小写
     *
     * @param s String
     * @return String
     */
    public static String toLowerCaseFirst(String s) {
        char[] chars = s.toCharArray();
        if (chars.length == 0) {
            return s;
        }
        chars[0] = toLowerCase(chars[0]);
        return String.valueOf(chars);
    }

    /**
     * 首字母大写
     *
     * @param s String
     * @return String
     */
    public static String toUpperCaseFirst(String s) {
        char[] methodName = s.toCharArray();
        methodName[0] = toUpperCase(methodName[0]);
        return String.valueOf(methodName);
    }

    /**
     * 字符转成大写
     *
     * @param chars char
     * @return char
     */
    public static char toUpperCase(char chars) {
        if (Character.isLowerCase(chars)) {
            return Character.toUpperCase(chars);
        }
        return chars;
    }

    /**
     * 字符转小写
     *
     * @param chars char
     * @return char
     */
    public static char toLowerCase(char chars) {
        if (Character.isUpperCase(chars)) {
            return Character.toLowerCase(chars);
        }
        return chars;
    }

    /**
     * 洗牌
     *
     * @param arr 待随机数组
     */
    public static <T> void shuffle(T[] arr) {
        shuffle(arr, null);
    }

    /**
     * 洗牌
     *
     * @param arr    待随机数组
     * @param random 随机种子
     */
    public static <T> void shuffle(T[] arr, Random random) {
        if (random == null) {
            random = getRandom();
        }
        for (int i = arr.length; i > 1; i--) {
            swap(arr, i - 1, random.nextInt(i));
        }
    }

    /**
     * 得到随机种子
     *
     * @return {@link Random}
     */
    @SneakyThrows
    public static Random getRandom() {
        return SecureRandom.getInstanceStrong();
    }

    /**
     * 交换
     *
     * @param arr 待交换数组
     * @param i   i
     * @param j   j
     */
    public static <T> void swap(T[] arr, int i, int j) {
        T tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    /**
     * 得到序列
     *
     * @param length 长度
     * @return {@link List}<{@link Integer}>
     */
    public static List<Integer> getSequence(int length) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < length; i++) {
            list.add(i);
        }
        return list;
    }

    /**
     * 截取
     *
     * @param list  列表
     * @param start 开始
     * @param end   结束
     * @return {@link List}<{@link T}>
     */
    public static <T> List<T> splice(List<T> list, int start, int end) {
        if (list == null) {
            return Collections.emptyList();
        }
        if (list.isEmpty()) {
            return new ArrayList<>(0);
        }
        int size = list.size();
        if (start < 0) {
            start += size;
        }
        if (end < 0) {
            end += size;
        }
        if (start == size) {
            return new ArrayList<>(0);
        }
        if (start > end) {
            int tmp = start;
            start = end;
            end = tmp;
        }
        if (end > size) {
            if (start >= size) {
                return new ArrayList<>(0);
            }
            end = size;
        }
        List<T> result = new ArrayList<>();
        for (int i = start; i < end; i++) {
            result.add(list.get(i));
        }
        if (end > start) {
            list.subList(start, end).clear();
        }
        return result;
    }

    public static boolean isEmpty(@Nullable Object str) {
        return str == null || "".equals(str);
    }
}